/*--- lzwunc.c ---------------------------- Listing 9-10 -------
 * Decompress an LZW-compressed file
 *
 * Entry point:     int LZWDecode(FILE *infile, FILE *outfile)
 *
 *                  Will decompress infile into outfile. Both
 *                  files should already be open in binary mode.
 *                  They will be closed by LZWDecode().
 *
 *                  LZWDecode() returns:
 *                      0: success
 *                      1: invalid signature byte
 *                      2: bad malloc
 *                      3: read error
 *                      4: write error
 *
 *
 * Switches:        DRIVER - compiles a test driver
 *                  CODES  - displays input codes
 *-------------------------------------------------------------*/
#define DRIVER

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lzw.h"

#if defined( CODES )
unsigned long outcount;
#endif

/* structure for data dictionary */
struct Dictionary {
    unsigned char c;
    CodeType parent;
};

/* decode buffer */
static unsigned char *DecodeBuffer;
static unsigned DecodeBufferSize = 0;
static unsigned LZWLoadBuffer( unsigned, CodeType );

/* the input routines and data storage routines */
#if BITS == 12
/* These are the 12-bit input routines */
/* data dictionary */
struct Dictionary dictionary[ MAX_CODE ];

static int havenibble;
static int olddata;
static CodeType incode;
static int LZWInInit ( void )
{
    havenibble = 0;
    if ( ! DecodeBufferSize )
    {
        DecodeBufferSize = 1000;
        DecodeBuffer = malloc( DecodeBufferSize );
        if ( DecodeBuffer == NULL )
            return ( 2 );
    }
#  if defined( CODES )
    outcount = 0;
#  endif
    return 0;
}

static int LZWIn ( FILE *infile )
{
    int data;
    if ( havenibble )
    {
        incode = olddata << 8;
        if (( data = fgetc ( infile )) == EOF )
            return ( 4 );
        incode |= ( data & 0xFF );
        havenibble = 0;
#  if defined( CODES )
        {
            unsigned count;
            printf ( "%6ld (1): %4x, \"", outcount, incode );
            count = LZWLoadBuffer ( 0, incode );
            while ( count )
                if ( DecodeBuffer[ --count ] < 32 )
                    printf ( "'0x%X'", DecodeBuffer[ count ] );
                else
                    fputc ( DecodeBuffer[ count ], stdout );
            printf ( "\"\n" );
        }
#  endif
        return ( 0 );
    }
    else
    {
        if (( data = fgetc ( infile )) == EOF )
            return ( 4 );
        incode = ( data & 0xFF ) << 4;
        if (( data = fgetc ( infile )) == EOF )
            return ( 4 );
        incode |= ( data >> 4 ) & 0xF;
#  if defined( CODES )
        {
            unsigned count;
            printf( "%6ld (0): %4x, \"", outcount, incode );
            count = LZWLoadBuffer ( 0, incode );
            while ( count )
                if ( DecodeBuffer[ --count ] < 32 )
                    printf ( "'0x%X'", DecodeBuffer[ count ] );
                else
                    fputc ( DecodeBuffer[ count ], stdout );
            printf ( "\"\n" );
        }
#  endif
        olddata = data & 0xF;
        havenibble = 1;
        return ( 0 );
    }
}

#elif BITS == 16
/* These are the 16-bit routines */
/* data dictionary */
struct Dictionary *dictionary;

static CodeType incode;
static int LZWInInit( void )
{
#  if defined( CODES )
    outcount = 0;
#  endif
    if (!DecodeBufferSize)
    {
        DecodeBufferSize = 1000;
        DecodeBuffer = malloc ( DecodeBufferSize );
        if ( DecodeBuffer == NULL )
            return ( 2 );
    }
    dictionary = malloc( DICTIONARY_SIZE *
                    sizeof( struct Dictionary ) );
    if ( dictionary == NULL )
        return ( 2 );

    return ( 0 );
}

static int LZWIn ( FILE *infile )
{
    if ( fread ( &incode, sizeof( CodeType ), 1, infile ) != 1 )
        return ( 4 );
#  if defined( CODES )
    {
        unsigned count;
        printf ( "%6ld (0): %4x, \"", outcount, incode );
        count = LZWLoadBuffer ( 0, incode );
        while ( count )
            if ( DecodeBuffer[ --count ] < 32 )
                printf ( "'0x%X'", DecodeBuffer[ count ] );
            else
                fputc( DecodeBuffer[ count ], stdout );
        printf( "\"\n" );
    }
#  endif
    return ( 0 );
}

#else
#  error Undefined value for BITS!
#endif

/* the actual decompression routine */
static IndexType freecode;
static unsigned LZWLoadBuffer ( unsigned count, CodeType code )
{
    if ( code >= freecode )
    {
        printf( "LZWLoad: code %u out of range!", code );
        return ( 0 );
    }
    while ( code >= PRESET_CODE_MAX )
    {
        DecodeBuffer[ count++ ] = dictionary[ code ].c;
        if ( count == DecodeBufferSize )
        {
            DecodeBuffer =
               realloc ( DecodeBuffer, DecodeBufferSize + 1000 );
            if ( ! DecodeBuffer )
            {
                /* out of memory */
                DecodeBufferSize = 0;
                return ( 0 );
            }
            else
                DecodeBufferSize += 1000;
        }
        code = dictionary[ code ].parent;
    }
    DecodeBuffer[ count++ ] = code;
    return ( count );
}

int LZWDecode ( FILE *infile, FILE *outfile )
{
    char buffer[ 10 ];
    int retval = 0;
    unsigned int inchar;
    unsigned count;
    CodeType oldcode;

    /* check the signature */
    fgets ( buffer, strlen ( SIGNATURE ) + 2, infile );
    if ( strcmp ( buffer, SIGNATURE ))
    {
        retval = 1;
        goto done;
    }

    /* prime the pump */
    if ( retval = LZWInInit() )
        goto done;
 priming:
    freecode = STARTING_CODE;
    if ( retval = LZWIn ( infile ))
        goto done;
    if ( incode == END_OF_INPUT )
        goto done;

    /* the first character always is itself */
    oldcode = incode;
    inchar = incode;
    if ( fputc( incode, outfile ) == EOF )
    {
        retval = 4;
        goto done;
    }
#if defined( CODES )
    outcount += 1;
#endif

    while ( ! ( retval = LZWIn ( infile )))
    {
        if ( incode == END_OF_INPUT )
            break;
        if ( incode == NEW_DICTIONARY )
            goto priming;
        if ( incode >= freecode )
        {
            /* We have a code that's not in our dictionary! */
            /* This can happen only one way--see text */

            count = LZWLoadBuffer ( 1, oldcode );

            /* Make last char same as first. Can use either */
            /* inchar or the DecodeBuffer[count-1] */

            DecodeBuffer[ 0 ] = inchar;
        }
        else
            count = LZWLoadBuffer ( 0, incode );

        if ( count == 0 )
            return ( 2 ); /* had a memory problem */

        inchar = DecodeBuffer[ count - 1 ];
        while ( count )
        {
            if ( fputc ( DecodeBuffer[--count], outfile) == EOF )
            {
                retval = 4;
                goto done;
            }
#if defined( CODES )
            outcount += 1;
#endif
        }

        /* now, update the dictionary */
        if ( freecode < MAX_CODE )
        {
            dictionary[ freecode ].parent = oldcode;
            dictionary[ freecode ].c = inchar;
            freecode += 1;

#if defined( CODES )
            {
                unsigned cnt;
                printf( "    just added code %5u: \"",
                                freecode - 1 );
                cnt = LZWLoadBuffer ( 0, freecode - 1 );
                while ( cnt )
                    if ( DecodeBuffer[ --cnt ] < 32 )
                        printf ( "'0x%X'", DecodeBuffer[ cnt ] );
                    else
                        fputc ( DecodeBuffer[ cnt ], stdout );
                printf( "\"\n" );
            }
#endif
        }
        oldcode = incode;
    }

 done:
    fclose ( infile );
    fclose ( outfile );
    return ( retval );
}

#if defined( DRIVER )
int main ( int argc, char *argv[] )
{
    FILE * infile, *outfile;
    int retval;

    if ( argc != 3 )
    {
        fprintf ( stderr, "Usage: lzwunc infile outfile\n" );
        return ( EXIT_FAILURE );
    }

    infile = fopen ( argv[ 1 ], "r+b" );
    if ( infile == NULL )
    {
        fprintf ( stderr, "can't open %s for input\n", argv[1] );
        return ( EXIT_FAILURE );
    }

    outfile = fopen ( argv[ 2 ], "w+b" );
    if ( outfile == NULL )
    {
        fprintf( stderr, "can't open %s for output\n", argv[2] );
        return ( EXIT_FAILURE );
    }

    if ( retval = LZWDecode( infile, outfile ))
    {
        printf( "uncompression failed: " );
        if ( retval == 1 )
            printf ( "invalid signature bytes\n" );
        else
        if ( retval == 2 )
            printf ( "malloc failed\n" );
        else
        if ( retval == 3 )
            printf ( "read error in data\n" );
        else
        if ( retval == 4 )
            printf ( "write error on output\n" );
        else
            printf ( "\n" );
        return ( retval );
    }
    else
    {
        printf ( "%s was expanded into %s\n",
                                       argv[ 1 ], argv[ 2 ] );
        return ( 0 );
    }
}
#endif
